package tuding.android.bigplanettracks;

import tuding.android.bigplanettracks.maps.Logex;
import tuding.android.bigplanettracks.maps.MarkerManager;
import tuding.android.bigplanettracks.maps.PhysicMap;
import tuding.android.bigplanettracks.maps.Place;
import tuding.android.bigplanettracks.util.Preferences;
import tuding.android.bigplanettracks.maps.tuding.AutoPause;
import tuding.android.bigplanettracks.maps.tuding.InfoWindow;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.Point;
import android.hardware.GeomagneticField;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.PowerManager.WakeLock;
import android.util.Log;
//import android.widget.Toast;

public class MyLocationService extends Service implements LocationListener, SensorEventListener{
	
	private NotificationManager mNotificationManager;
	private SensorManager mSensorManager;
	private WakeLock wakeLock;
	
	private long minTime; // ms
	private float minDistance; // m
	private Handler locationHandler;
	private static int ignorCounter = 0;

	private double variation; // Magnetic variation.
	
	public MyLocationService() {
		minTime = BigPlanet.minTime;
		minDistance = BigPlanet.minDistance;
		locationHandler = BigPlanet.locationHandler;
	}
		
	@Override
	public void onCreate() {
		super.onCreate();
		Log.d("MyLocationService", "Service: onCreate()");
		mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
		if (locationHandler == null) {
			// stop service to avoid error when recording a track and being killed manually
			Intent intent = new Intent(this, MyLocationService.class);
			this.stopService(intent);
		} else {
			if (!BigPlanet.isGPSTracking) {
				// stop service to avoid error when recording a track and being killed manually
				Intent intent = new Intent(this, MyLocationService.class);
				this.stopService(intent);
				clearNotification();
			} else {
				// when normally start service
				showNotification();
				acquireWakeLock();
			}
		}
	}
	
	@Override
	public void onDestroy() {
		super.onDestroy();
		Log.d("MyLocationService", "Service: onDestroy()");
		clearNotification();
		releaseWakeLock();
	}
	
	public void registerSensor(Context context) {
		if (BigPlanet.currentLocation != null)
			setVariation(BigPlanet.currentLocation);
		
		mSensorManager = (SensorManager)context.getSystemService(SENSOR_SERVICE);
		if (mSensorManager != null) {
			Sensor compass = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
			if (compass != null) {
				mSensorManager.registerListener(this, compass, SensorManager.SENSOR_DELAY_UI);
//				Log.d("MyLocationService", "SensorManager.registerListener()");
			}
		}
	}

	protected void unregisterSensor() {
		if (mSensorManager != null) {
			mSensorManager.unregisterListener(this);
			mSensorManager = null;
//			Log.d("MyLocationService", "SensorManager.unregisterListener()");
		}
	}
	
	@Override
	public IBinder onBind(Intent i) {
		return null;
	}
	
	private void showNotification() {
		int iconId = 0;
		String contentTitle = null;
		String contentText = null;
		
		iconId = R.drawable.globe;
		contentTitle = getString(R.string.app_name);
		contentText = getString(R.string.notify_recording);
		
		Intent notifyIntent = new Intent(this, BigPlanet.class);
		PendingIntent pendingIntent = PendingIntent.getActivity(
				this, 0, notifyIntent, Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);

		Notification notification = new Notification();
		notification.flags = Notification.FLAG_NO_CLEAR;
		notification.icon = iconId;
		notification.defaults = Notification.DEFAULT_SOUND;
		
		notification.setLatestEventInfo(this, contentTitle, contentText, pendingIntent);
		startForeground(1, notification);
		mNotificationManager.notify(1, notification);
	}
	
	private void clearNotification() {
		stopForeground(true);
		mNotificationManager.cancel(1);
	}
	
	private void acquireWakeLock() {
		PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
		if (wakeLock == null) {
			wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "wakeLock");
			if (!wakeLock.isHeld()) {
				wakeLock.acquire();
			}
		}
	}
	
	private void releaseWakeLock() {
		if (wakeLock != null && wakeLock.isHeld()) {
			wakeLock.release();
			wakeLock = null;
		}
	}
	
	private void moveMarker(Location location) {
		//addMarker(location, PhysicMap.getZoomLevel());
		Log.i("TRACK", "moveMarker: --->");
		Message m = locationHandler.obtainMessage(BigPlanet.MethodAddMarker, 0, 0, location);
		locationHandler.sendMessage(m);
		//mapControl.invalidate(); // not works if leaving activity and entering again
		//mapControl.updateScreen(); // works
		m = locationHandler.obtainMessage(BigPlanet.MethodUpdateScreen, 0, 0, null);
		locationHandler.sendMessage(m);
	}
	
	@Override
	public void onLocationChanged(Location location) {
		
		// for gps fix routine:
		if( location == null 
		|| (BigPlanet.isGPSTracking && (
			   !(location.hasAccuracy())
			 || (location.hasAccuracy() && location.getAccuracy() == 0)
			 || location.getTime() == 0 )
			)
		 ) {
			Log.w("GPS", "location is not qulified");
			return;
		}
		Log.i("GPS", "Acc is "+location.getAccuracy());
		
		int targetTimeIntv = Preferences.getGPSIntervalValue();
		
		if( location.hasAccuracy() && location.getAccuracy()<50 ) {
			// remove location update req and register new when the acc is 50
			try {
				BigPlanet.locationManager.removeUpdates(BigPlanet.gpsLocationListener);
			} catch(Exception e) {
				e.printStackTrace();
			}
			Logex.message("REMOVING", "location update is removed");
			Message upmsg = new Message(); 
			upmsg.arg1 = targetTimeIntv;
			upmsg.what = 2;
			BigPlanet.updateLocationUpdateHandler.sendMessageDelayed(upmsg, targetTimeIntv);
		}
		
		//
		if( BigPlanet.StoreInProgress == false 
		 && location.distanceTo(BigPlanet.currentLocation) > 10
		 && ( !BigPlanet.isGPSTracking 
			||(BigPlanet.isGPSTracking && System.currentTimeMillis() - BigPlanet.currentTimeMillis > 4000)
			)
		   ) {
		
			// if it store markers to sd, then igore current update
			BigPlanet.StoreInProgress = true;
			ignorCounter = 0;
		
			if (BigPlanet.currentLocation == null
					|| location.distanceTo(BigPlanet.currentLocation) > 1000) {
				setVariation(location);
			}
			
			// AutoPause function add
			// when it is autoPaused, then if location is changed, no matter what acc, then restore pref setting
			if(AutoPause.PauseGear > AutoPause.getGearFromGPSIntervalValue()) {
				if( (location.hasAccuracy() && location.getAccuracy()<250) ) {
					AutoPause.PauseGear = AutoPause.getGearFromGPSIntervalValue();
				    BigPlanet.autoPause.AP_sleep_lighter();
				} else {
					Log.i("AP", "Location not qulified to regard change!");
				}
			}
			if(location.hasAccuracy() && location.getAccuracy()<50) {
				Log.i("AP", "location changed & qulified");
				// to avoid sleep
				BigPlanet.autoPause.setLocationChangedFlag(true);
			}
			
			
			BigPlanet.currentLocation = location;
			BigPlanet.currentTimeMillis = System.currentTimeMillis();
			BigPlanet.inHome = true;
			BigPlanet.isMapInCenter = false;
			if (!BigPlanet.isGPSTracking) {
				if (BigPlanet.isFollowMode) {
					//goToMyLocation(location, PhysicMap.getZoomLevel());
					Message m = locationHandler.obtainMessage(BigPlanet.MethodGoToMyLocation, 0, 0, location);
					locationHandler.sendMessage(m);
				} else {
					moveMarker(location);
				}
			} else { // isGPSTracking = true
				if ( (location.hasAccuracy() && location.getAccuracy()<50)
				  //|| !location.hasAccuracy() 
				  //|| (location.getSpeed() > 6 && (location.getSpeed()*20) > location.getAccuracy())
				  ) {
					int thisdistance = 0;
					int thisrise = 0;
					if(BigPlanet.previousLocation != null) {
						thisdistance = (int)location.distanceTo(BigPlanet.previousLocation);
						thisrise = (int)(location.getAltitude() - BigPlanet.previousLocation.getAltitude());
					}
					if(location.getSpeed()>InfoWindow.STATS_maxspeed) InfoWindow.STATS_maxspeed = location.getSpeed();
					if(location.getAltitude()>InfoWindow.STATS_maxalt) InfoWindow.STATS_maxalt = (int)location.getAltitude();
					InfoWindow.STATS_totaldistance += thisdistance;
					if(thisrise>0) InfoWindow.STATS_totalrise += thisrise;
					else InfoWindow.STATS_totalfall -= thisrise;
					InfoWindow.STATS_totaltime = System.currentTimeMillis() - BigPlanet.recordingTime;
					

				// when the speed is high enough, low accuracy is acceptable
				//if ((location.hasAccuracy() && (location.getSpeed()*20)<=location.getAccuracy()) || !location.hasAccuracy()) {
					//Log.i("TRACK", "Notice: delta-distance is "+deltadist+" current speed is "+currentspeed);
					//if(deltadist > currentspeed) {
					if(BigPlanet.Paused_flag == false) {
						if (BigPlanet.isFollowMode) {
							//trackMyLocation(location, PhysicMap.getZoomLevel());
							Message m = locationHandler.obtainMessage(BigPlanet.MethodTrackMyLocation, 0, 0, location);
							locationHandler.sendMessage(m);
						} else {
							moveMarker(location);
						}
					} else { // activity is paused, the BigPlanet may be not there anymore
						Point myGPSOffset = Preferences.getGPSOffset();
						double latFix = location.getLatitude() + myGPSOffset.y*Math.pow(10, -5);;
						double lonFix = location.getLongitude() + myGPSOffset.x*Math.pow(10, -5);
						Place place = new Place();
						place.setLat(latFix);
						place.setLon(lonFix);
						place.setLocation(location); // so location is none-fixed location
						MarkerManager.addMarker(place, PhysicMap.getZoomLevel(), MarkerManager.DrawMarkerOrTrack, MarkerManager.MY_LOCATION_MARKER);
					}
					// update location change interval time based on speed
					//int targetTimeIntv = getTimeIntvOnSpeed(location.getSpeed());
					
					BigPlanet.previousLocation = location;
				} else { // when networklistener whose accuracy is less than 50, will come here
					BigPlanet.StoreInProgress = false;
					Message msg = new Message(); 
					msg.what = 1;
					BigPlanet.updateInfoWindowHandler.sendMessage(msg);
				}
			}
			
			


		} else {
			if(++ignorCounter > 10) {
				ignorCounter = 0;
				//Toast.makeText(BigPlanet.class., getString(R.string.SDcard_cannot_respond_so_quickly), Toast.LENGTH_LONG).show();
			}
			Log.i("TRACK", "StoreInProgress or location change is not big enough, ignorCounter="+ignorCounter);
		}
	}
	
	private int getTimeIntvOnSpeed(float speed) {
		if(speed < 4) { // on foot
			return Preferences.getGPSIntervalValue();
		} else if(speed < 10) { // by bike
			return (BigPlanet.BikeIntvTime>Preferences.getGPSIntervalValue())?BigPlanet.BikeIntvTime:Preferences.getGPSIntervalValue();
		} else if(speed < 20) { // driving with high speed
			return (BigPlanet.CarIntvTime>Preferences.getGPSIntervalValue())?BigPlanet.CarIntvTime:Preferences.getGPSIntervalValue();
		} else { // high way or fly
			return (BigPlanet.FlyIntvTime>Preferences.getGPSIntervalValue())?BigPlanet.FlyIntvTime:Preferences.getGPSIntervalValue();
		}
	}
	
	
	@Override
	public void onProviderDisabled(String provider) {
		Log.i("Location", provider + " is disabled.");
		if (provider.equals("gps")) {
			String networkProvider = LocationManager.NETWORK_PROVIDER;
			try {
				BigPlanet.locationManager.requestLocationUpdates(networkProvider, minTime, minDistance, BigPlanet.networkLocationListener);
				Log.i("Location", networkProvider +" requestLocationUpdates() "+ minTime +" "+ minDistance);
			} catch (RuntimeException e) {
			}
		}
	}
	
	@Override
	public void onProviderEnabled(String provider) {
		Log.i("Location", provider + " is enabled.");
		if (provider.equals("gps")) {
			try {
				BigPlanet.locationManager.requestLocationUpdates(provider, minTime, minDistance, BigPlanet.gpsLocationListener);
			} catch (RuntimeException e) {
			}
		} else {
			try {
				BigPlanet.locationManager.requestLocationUpdates(provider, minTime, minDistance, BigPlanet.networkLocationListener);
			} catch (RuntimeException e) {
			}
		}
		Log.i("Location", provider +" requestLocationUpdates() "+ minTime +" "+ minDistance);
	}
	
	@Override
	public void onStatusChanged(String provider, int status, Bundle extras) {
		int numSatellites = extras.getInt("satellites", 0);
		BigPlanet.locationProvider = provider+" "+status+" "+numSatellites;
		if (status == 0) {
			Log.i("Location", provider + " is OUT OF SERVICE");
		} else if (status == 1) {
			Log.i("Location", provider + " is TEMPORARILY UNAVAILABLE");
			// invoke network's requestLocationUpdates() if not tracking
			if (provider.equals("gps") && !BigPlanet.isGPSTracking) {
				String networkProvider = LocationManager.NETWORK_PROVIDER;
				try {
					BigPlanet.locationManager.requestLocationUpdates(networkProvider, minTime, minDistance, BigPlanet.networkLocationListener);
					Log.i("Location", networkProvider +" requestLocationUpdates() "+ minTime +" "+ minDistance);
				} catch (RuntimeException e) {
				}
			}
		} else {
			Log.i("Location", provider + " is AVAILABLE");
			// gpsLocationListener has higher priority than networkLocationListener
			/*
			if (provider.equals("gps")) {
				if (BigPlanet.networkLocationListener != null) 
				    BigPlanet.locationManager.removeUpdates(BigPlanet.networkLocationListener);
			}*/
		}
		//provider = provider+" "+status+" "+numSatellites;
		//String title = BigPlanet.getTitle(provider);
		if (BigPlanet.titleHandler != null) {
			Message m = new Message();
			BigPlanet.titleHandler.sendMessage(m);
		}
	}
	
	private void setVariation(Location location) {
		long timestamp = location.getTime();
		if (timestamp == 0) {
			// Hack for Samsung phones which don't populate the time field
			timestamp = System.currentTimeMillis();
		}

		GeomagneticField field = new GeomagneticField(
				(float) location.getLatitude(),
				(float) location.getLongitude(),
				(float) location.getAltitude(), timestamp);
		variation = field.getDeclination();
		
//		Log.d("MyLocationService", "Variation reset to " + variation + " degrees.");
	}

	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {
		// do nothing
	}

	@Override
	public void onSensorChanged(SensorEvent event) {
		synchronized (this) {
			float magneticHeading = event.values[0];
			double heading = magneticHeading + variation;
			if (BigPlanet.setHeading((float) heading)) {
				//mapControl.invalidate(); // not works if leaving activity and entering again
				//mapControl.updateScreen(); // works
				Message m = locationHandler.obtainMessage(BigPlanet.MethodUpdateScreen, 0, 0, null);
				locationHandler.sendMessage(m);
			}
		}
	}
	
}